package lumis.portal.presentation;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;

import lumis.portal.PortalContext;
import lumis.portal.PortalException;
import lumis.portal.PortalRequestParameters;
import lumis.portal.PortalStringResource;
import lumis.portal.authentication.SessionConfig;
import lumis.portal.manager.ManagerFactory;
import lumis.portal.page.PageConfig;
import lumis.portal.page.PageLayoutUtil;
import lumis.portal.page.PageWebResource;
import lumis.portal.presentation.PresentationControllerHtml.DataPreviewMode;
import lumis.portal.presentation.core.LayoutFilePage;
import lumis.portal.presentation.perspective.Perspective;
import lumis.portal.servicecontainer.ServiceContainerRenderRequest;
import lumis.portal.servicecontainer.ServiceContainerRenderResponse;
import lumis.portal.serviceinterfaceinstance.ServiceInterfaceInstanceConfig;
import lumis.portal.transaction.ITransaction;
import lumis.portal.transaction.PortalTransactionFactory;
import lumis.util.IResource;
import lumis.util.XmlUtil;
import lumis.util.log.ILogger;
import lumis.util.log.LoggerFactory;

import org.apache.wicket.protocol.http.WebRequest;
import org.apache.wicket.protocol.http.WebResponse;
import org.w3c.dom.Document;
import org.w3c.dom.Node;

/**
 * Contains utility methods to help the page render.
 *
 * @version $Revision: 13093 $ $Date: 2011-05-28 18:40:18 -0300 (Sat, 28 May 2011) $
 * @since 5.6.0
 */
public class PageRendererUtil
{
	/**
	 * @since 6.0.0
	 */
	private static final Pattern LAYOUT_FILE_FORMAT_INTERFACE_INSTANCES_PATTERN = Pattern.compile("<interface id=\"([^\"]+)\"");

	private static final ILogger logger = LoggerFactory.getLogger(PageRendererUtil.class);

	/**
	 * Returns the HTML used for a service interface instance rendering
	 * in safe mode.
	 * @return the HTML code.
	 * @since 5.6.0
	 */
	public static String getServiceInterfaceInstanceSafeModeHTML()
	{
		IResource resource = new PortalStringResource();
		resource.addResourcePath("lumis/portal/strings/adminstrings");
		try
		{
			return "<div class=\"cLumSafeMode\">" + ManagerFactory.getLocalizationManager().localize("STR_SAFE_MODE", resource) + "</div>";
		}
		catch (PortalException e)
		{
			throw new RuntimeException(e);
		}		
	}
	
	/**
	 * Returns the HTML source code for the service interface instance instantiated on a page.
	 * 
	 * @param serviceInterfaceInstanceId the service interface instance identifier.
	 * @param page the page that contains the service instance to be rendered.
	 * @return the HTML source code for the service interface instance instantiated on a page.
	 * @since 6.0.0
	 */
	public static CharSequence renderServiceInterfaceInstance(String serviceInterfaceInstanceId, LayoutFilePage page)
	{
		try
		{
			
			PageWebResource webResource = page.getPageWebResource();
			SessionConfig sessionConfig = SessionConfig.getCurrentSessionConfig();

			if (page.isSafeMode())
			{
				return PageRendererUtil.getServiceInterfaceInstanceSafeModeHTML();
			}
			
			HttpServletRequest request = ((WebRequest)page.getRequest()).getHttpServletRequest();
			
			PortalRequestParameters parameters = page.getParameters();
			if(parameters == null)
			{
				parameters = new PortalRequestParameters(sessionConfig, PortalTransactionFactory.getCurrentTransaction());
			}
			
			ServiceContainerRenderRequest renderRequest = new ServiceContainerRenderRequest(sessionConfig, parameters, request);
			ServiceContainerRenderResponse renderResponse = new ServiceContainerRenderResponse(renderRequest, ((WebResponse)page.getResponse()).getHttpServletResponse());

			renderRequest.setPrinting(page.isPrinting());
			renderRequest.setSessionConfig(sessionConfig);
			renderRequest.setPageWebResource(webResource);
			renderRequest.setMode(page.getPortalMode().getPerspective().value());
			
			ServiceInterfaceInstanceConfig serviceInterfaceInstanceConfig = ManagerFactory.getServiceInterfaceInstanceManager().get(sessionConfig, serviceInterfaceInstanceId, PortalTransactionFactory.getCurrentTransaction());
			DataPreviewMode dataPreviewMode = page.getDataPreviewMode();
			if(dataPreviewMode.previewModeEnabled)
			{
				if(page.getHighlightInterfaceInst() == null)
				{
					renderRequest.setPreview(dataPreviewMode.previewModeEnabled);
				}
				else
				{
					ServiceInterfaceInstanceConfig highlightII = ManagerFactory.getServiceInterfaceInstanceManager().get(sessionConfig, page.getHighlightInterfaceInst(), PortalTransactionFactory.getCurrentTransaction());
					if(serviceInterfaceInstanceId.equals(page.getHighlightInterfaceInst()) || 
							(serviceInterfaceInstanceConfig.getServiceInterfaceId().equals(highlightII.getServiceInterfaceId()) && 
							serviceInterfaceInstanceConfig.getServiceInstanceId().equals(highlightII.getServiceInstanceId())))
					{
						renderRequest.setPreview(dataPreviewMode.previewModeEnabled);
					}
				}
			}
			
			renderRequest.setPageCacheId(page.getGeneratorPageCacheId());
			renderResponse.setPageWebResource(webResource);

			renderRequest.setServiceInterfaceInstanceId(serviceInterfaceInstanceId);
			renderResponse.setServiceInterfaceInstanceId(serviceInterfaceInstanceId);
			renderRequest.setCallerServiceInterfaceInstanceId(request.getParameter(PortalRequestParameters.PAGE_PARAMETER_CALLER_INTERFACE_INST));

			if (serviceInterfaceInstanceId.equals(page.getExecutedProcessActionServiceInterfaceInstanceId()))
			{
				renderRequest.setParameterFromProcessAction(true);
			}
			else if (serviceInterfaceInstanceId.equals(page.getMultiPartFormServiceInterfaceInstance()))
			{
				renderRequest.setServiceInterfaceInstanceParameters(page.getMultiPartFormParameters());
				renderRequest.setParameterFromProcessAction(true);
			}

			try
			{
				return PortalContext.getServiceContainer().renderServiceInterfaceInstance(renderRequest, renderResponse, PortalTransactionFactory.getCurrentTransaction());
			}
			catch (PortalException e)
			{
				// no need to log, since serviceContainer already logged
				return "<!-- exception thrown rendering lumis portal interface instance: "+serviceInterfaceInstanceId+" -->";
			}
		}
		catch (Throwable e)
		{
			logger.error("<!-- exception thrown rendering lumis portal interface instance: "+serviceInterfaceInstanceId+" -->", e);
			return "<!-- exception thrown rendering lumis portal interface instance: "+serviceInterfaceInstanceId+" -->";
		}
	}

	/**
	 * Returns current page's layout {@link Document document}. Result is
	 * cached during the request's live time.
	 * 
	 * @return current page's layout.
	 * @since 6.0.0
	 */
	public static Document getPageLayout(SessionConfig sessionConfig, PageWebResource pageWebResource, ITransaction transaction, int mode)
	{
		try
		{
			PageConfig pageConfig = pageWebResource.getPage();
			// check if a run time interface is specified. If so, make sure that
			// this page has an instance of the runtime interface and change the
			// layout to include this interface instance
			if (pageWebResource.getRunTimeServiceInterfaceId() != null)
			{
				ServiceInterfaceInstanceConfig serviceInterfaceInstanceConfig = ManagerFactory.getServiceInterfaceInstanceManager()
						.getOrAddServiceInterfaceRunTimeInstance(sessionConfig, pageWebResource.getRunTimeServiceInterfaceId(),
								pageWebResource.getRunTimeServiceInstanceId(), pageConfig.getId(),
								PortalTransactionFactory.getCurrentTransaction());
	
				String layout = XmlUtil.XML_HEADER;
				layout += "<layout><area><column width=\"100%\">";
	
				layout += "<row><cell>" + serviceInterfaceInstanceConfig.getId() + "</cell></row>";
				layout += "</column></area></layout>";
	
				pageConfig.setLayout(layout);
			}
	
			String layout = pageConfig.getLayout();
			if (layout == null)
				layout = "<layout><area><column width=\"100%\"><row/></column></area></layout>";
			else
			{
				if (getLayoutFilePath(pageWebResource) == null)
				{
					if (layout.contains("<holder"))
					{
						// convert new format to old one..
						Matcher matcher = LAYOUT_FILE_FORMAT_INTERFACE_INSTANCES_PATTERN.matcher(layout);
						StringBuilder sb = new StringBuilder(200);
						boolean found = false;
						while (matcher.find())
						{
							found = true;
							sb.append("<row><cell>");
							sb.append(matcher.group(1));
							sb.append("</cell></row>");
						}
						if (found)
						{
							sb.insert(0, "<column width=\"100%\">");
							sb.insert(0, "<area>");
							sb.insert(0, "<layout>");
							sb.append("</column>");
							sb.append("</area>");
							sb.append("</layout>");
							layout = sb.toString();
						}
						else
							layout = "<layout><area><column width=\"100%\"><row/></column></area></layout>";
					}
				}
			}
			Document pageLayout = XmlUtil.getDocument(layout);
	
			if(mode==Perspective.ADMIN.value())
			{
				if (pageLayout.getElementsByTagName("area").getLength() == 0 && getLayoutFilePath(pageWebResource) == null)
				{
					Collection<String> holderIds = getPageLayoutHolderIds(pageLayout, pageWebResource);
					if (holderIds.isEmpty())
						return XmlUtil.getDocument("<layout><area><column width=\"100%\"><row/></column></area></layout>");
	
					StringBuilder sb = new StringBuilder(2048);
					sb.append(XmlUtil.XML_HEADER);
					sb.append("<layout>");
					for (String holderId : holderIds)
					{
						sb.append("<area><column width=\"100%\"><row>");
						for (String interfaceInstanceId : PageRendererUtil.getHolderServiceInterfaceInstanceIds(holderId, pageLayout))
						{
							sb.append("<cell>");
							sb.append(interfaceInstanceId);
							sb.append("</cell>");
						}
						sb.append("</row></column></area>");
					}
					sb.append("</layout>");
					pageLayout = XmlUtil.getDocument(sb.toString());
				}			
			}
			
			return pageLayout;
		}
		catch (PortalException e) 
		{
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * Returns the identifiers of the service interface instances in a holder.
	 * @param holderId the holder identifier.
	 * @return the service interface instance identifiers, ordered according
	 * to their place in the holder.
	 * @since 6.0.0
	 */
	public static List<String> getHolderServiceInterfaceInstanceIds(String holderId, Document layoutDocument)
	{
		try
		{
			// read the values from layout/holder/interface/@id
			Node[] interfaceNodes = XmlUtil.selectNodes("layout/holder[@id='" + holderId + "']/interface", layoutDocument);
			List<String> result = new ArrayList<String>(interfaceNodes.length);
			for (Node interfaceNode: interfaceNodes)
				result.add(XmlUtil.readAttributeString("id", interfaceNode));
			
			if (PageLayoutUtil.WITHOUT_HOLDER_ID.equals(holderId))
			{
				// read the values from layout/area/column/row/cell
				Node[] withoutHolderInterfaceIdNodes = XmlUtil.selectNodes("layout/area/column/row/cell", layoutDocument);
				for (Node interfaceIdNode: withoutHolderInterfaceIdNodes)
					result.add(interfaceIdNode.getTextContent());
			}
			
			return result;
		}
		catch (PortalException e)
		{
			throw new RuntimeException(e);
		}
	}

	/**
	 * Returns the path to the layout file.
	 * @return the path to the layout file, or <code>null</code> if no
	 * layout file is set.
	 * @since 6.0.0
	 */
	public static String getLayoutFilePath(PageWebResource pageWebResource)
	{
		String layoutFilePath;
		if(pageWebResource != null)
		{
			// never use layout file for runtime interface
			if (pageWebResource.getRunTimeServiceInterfaceId() != null)
			{
				layoutFilePath = null;
			}
			else
			{
				layoutFilePath = pageWebResource.getPage().getLayoutFilePath();
				if (layoutFilePath != null)
					layoutFilePath = PortalContext.getDefinitionPath(layoutFilePath);
			}
		}
		else
			layoutFilePath = null;
		
		return layoutFilePath;
	}
	
	/**
	 * Returns a collection of holder ids for the given page layout {@link Document} if the {@link PageWebResource} has a layout file path,
	 * else returns null.
	 * 
	 * @param pageLayout the page layout document.
	 * @param pageWebResource the page web resource.
	 * @return A collection of holder ids for the given page layout {@link Document} if the {@link PageWebResource} has a layout file path,
	 * else returns null.
	 * @since 6.0.0
	 */
	public static Collection<String> getPageLayoutHolderIds(Document pageLayout, PageWebResource pageWebResource)
	{
		if (getLayoutFilePath(pageWebResource) != null)
		{
			try
			{
				Node[] holderNodes = XmlUtil.selectNodes("layout/holder", pageLayout);
				Collection<String> result = new ArrayList<String>(holderNodes.length);
				for (Node holderNode: holderNodes)
					result.add(XmlUtil.readAttributeString("id", holderNode));
				
				// add WITHOUT_HOLDER_ID if any layout/area/column/row/cell exists
				if (XmlUtil.selectSingleNode("layout/area/column/row/cell", pageLayout) != null)
					result.add(PageLayoutUtil.WITHOUT_HOLDER_ID);
				
				return result;
			}
			catch (PortalException e)
			{
				throw new RuntimeException(e);
			}
		}
		else
		{
			return Collections.emptyList();
		}
	}
}
